package com.versiFramework{
	import com.versiFramework.controller.versiController;
	import com.versiFramework.core.IController;
	import com.versiFramework.core.IVersiClass;
	import com.versiFramework.core.IService;
	import com.versiFramework.core.ctrlEvent;
	import com.versiFramework.core.versiClassBean;
	import com.versiFramework.events.versiEvent;
	
	import flash.events.EventDispatcher;
	import flash.utils.describeType;
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;
	import flash.utils.getQualifiedSuperclassName;
	
	import mx.collections.ArrayCollection;
	
	public class versi extends EventDispatcher {
		//* properties *//
		public static const CTRL_ALL:String = "all";
		public static const VERSION:String='.2.04';

		private var _classes:Object = new Object;
		private var classTypeCrossRef:Object = new Object;	
		
		public function set classReference(instance:*):void //You can only add direct references to items with a 'name' property 
		{
			if (instance.hasOwnProperty("name"))
			{
				setClassReference(instance,instance.name,false)
			}
			else if (! instance.hasOwnProperty("name"))
			{
				throw new Error( "Class property 'name' is required to add a class reference directly. Did you mean to use the setClassReference function? " );
			}
		}
		
		//* static functions *//
		public static function classExists(classItem:*):Boolean
		{
			if (classItem is String ) 
			{
				return getInstance()._classes.hasOwnProperty(classItem)
			}
			else 
			{
				var type:String = getQualifiedClassName(classItem);
				for each (var item:versiClassBean in getInstance()._classes)
				{
					if (item.type == type)
					{
						return true; break;
					}
				}
				return false;
			}
		}
		
		public static function getClass(classItem:*,createOnMissing:Boolean=true,reference:Class=null):*
		{
			if (classItem is String) 
			{
				return getInstance().getClassByName(classItem,reference,createOnMissing)
			}
			else
			{
				return getInstance().getClassByType(classItem,createOnMissing)
			}
		}
		
		public static function registerClass(instance:*,name:String,overwrite:Boolean=false):void
		{
			getInstance().setClassReference(instance,name,overwrite)
		}
		
		public static function removeClass(instance:*):void
		{
			if (instance is String)
			{
				if (getInstance()._classes.hasOwnProperty(instance))
				{
					getInstance().removeClassInstance(getInstance()._classes[instance])
				}
			}
			else if (instance.hasOwnProperty("name") && getInstance()._classes.hasOwnProperty(instance.name)
				&& getInstance()._classes[instance.name].instance == instance)
			{
				getInstance().removeClassInstance(getInstance()._classes[instance.name])
			}
			else
			{
				getInstance().removeClassInstance(getInstance().getBeanByInstance(instance))
			}
		}
		
		public static function doEvent(type:String,data:*=null,target:String="all"):void
		{
			var self:versi = getInstance();
			if (target != "all" && self._classes.hasOwnProperty(target))
			{
				self.getClassByName(target).dispatchEvent( new versiEvent(type,data,true,true) );
			}
			else
			{
				self.dispatchEvent( new ctrlEvent(type,data,target) ); 
			}
		}
		
		//* public functions *//
		public function setClassReference(instance:*,name:String,overwrite:Boolean=false):void 
		{
			var classBean:versiClassBean
			if (_classes.hasOwnProperty(name) && overwrite)
			{
				removeClassReference(_classes[name]);
				classBean = _classes[name];
				classBean.instance = instance;
				classBean.type = getQualifiedClassName(instance);
				addClassReference(classBean);
			}
			else if ( ! _classes.hasOwnProperty(name))
			{
				classBean = new versiClassBean;
				classBean.name = name;
				classBean.instance = instance;
				classBean.type = getQualifiedClassName(instance)
				_classes[name]=classBean;
				addClassReference(classBean);
			}
		}
		
		public function doFunction(target:String,fun:String,args:Array=null):*
		{
			if (_classes.hasOwnProperty(target) && this._classes[target].instance.hasOwnProperty(fun))
			{
				return 	this._classes[target].instance[fun].apply(null,args)
			}
			return;
		}
		
		//* private functions *//
		private function getClassByName(name:String,classReference:Class=null,createOnMissing:Boolean=true):*
		{
			if (_classes.hasOwnProperty(name))
			{
				return _classes[name].instance;
			}
			else if (createOnMissing && classReference)
			{
				return createClassInstance(classReference,name)	
			}
		}
		
		private function getClassByType(classReference:Class,createOnMissing:Boolean=true):*
		{
			var type:String = getQualifiedClassName(classReference);
			var classObject:Object;
			/*
			for each (var item:versiClassBean in _classes)
			{
				if (item.type == type)
				{
					if (classObject) 
					{
						throw new Error( "Ambiguous reference to " + type );
					}
					else
					{
						classObject = item.instance;
					}
				}
			}
			*/
			
			if (this.classTypeCrossRef.hasOwnProperty(type))
			{
				if  (this.classTypeCrossRef[type].length == 1)
				{	
					classObject = getClassByName(this.classTypeCrossRef[type][0])
				}
				else
				{
					throw new Error( "Ambiguous reference to " + type );
				}
			}
			
			
			if (!classObject && createOnMissing)  
			{
				return createClassInstance(classReference)	
			}
			
			return classObject;
		}
		
		private function getBeanByInstance(instance:Class):versiClassBean
		{
			for each (var item:versiClassBean in _classes)
			{
				if (item.instance == instance)
				{
					return item;
				}
			}
			return null
		}	
		
		private function createClassInstance(classRef:Class,name:String=null):*
		{
			var ClassDefinition:Object = getDefinitionByName(getQualifiedClassName( classRef ));
			var ClassInstance:*;
			
			if (ClassDefinition.hasOwnProperty('getInstance'))
			{
				ClassInstance = ClassDefinition.getInstance();
			}
			else
			{
				ClassInstance = new ClassDefinition;
			}
			
			
			// check if the class instance we just created already has a name .. and if that name is already registered .. and if it's a singleton, then just it back w/o registering
			if (ClassInstance.hasOwnProperty('name') 
					&& String(ClassInstance.name).length 
						&& _classes.hasOwnProperty(ClassInstance.name)
							&& getClassByName(ClassInstance.name) == ClassInstance)
			{
				return ClassInstance;
			}
			else
			{				
				if (ClassInstance is IController)
				{
					if (!ClassInstance.name.length)	{ throw new Error( "Controller names cannot be undefined" ); }
					else if (name && name.length && name != ClassInstance.name)	{ throw new Error( "Controller name mismatch:" + ClassInstance.name + "!=" + name ); }
					else { name = ClassInstance.name; }
				}
				else if (ClassInstance is IVersiClass )
				{
					if (name) { ClassInstance.name = name; }
					else if (ClassInstance.name.length) { name = ClassInstance.name; }
					else
					{
						name = getQualifiedClassName(classRef).split(":").pop();
						ClassInstance.name = name;
					}
				}
				else
				{
					var classIntrospection:XML = describeType(ClassInstance);
					var accessorName:XMLList = classIntrospection.accessor.(@name=='name');
					var variableName:XMLList = classIntrospection.variable.(@name=='name');			
					
					if (name)
					{
						if (variableName.length() || (accessorName.length() && accessorName.@access=="readwrite"))
						{
							ClassInstance.name = name;
						}
					}
					else
					{
						name = getQualifiedClassName(classRef).split(":").pop();
						if (ClassInstance.hasOwnProperty('name') && String(ClassInstance.name).length)
						{
							name = ClassInstance.name;
						}
						else if (variableName.length() || (accessorName.length() && accessorName.@access=="readwrite"))
						{
							ClassInstance.name = name;
						}
					}
				}
								
				setClassReference(ClassInstance,name);
				return ClassInstance
			}
		}
	
		private function removeClassInstance(classBean:versiClassBean):void
		{
			if (classBean)
			{
				removeClassReference(classBean)
				if (classBean.instance.hasOwnProperty('cleanup'))
				{
					classBean.instance.cleanup();
				}
				classBean.instance == null;
				delete _classes[classBean.name];
			}
		}
		
		private function removeClassReference(classBean:versiClassBean):void
		{
			if (this.classTypeCrossRef.hasOwnProperty(classBean.type))	
			{
				if (this.classTypeCrossRef[classBean.type].length)
				{
					this.classTypeCrossRef[classBean.type].removeItemAt(this.classTypeCrossRef[classBean.type].getItemIndex(classBean.name))
				}
				if (this.classTypeCrossRef[classBean.type].length == 0)
				{
					delete this.classTypeCrossRef[classBean.type];
				}
			}
		}
		
		private function addClassReference(classBean:versiClassBean):void
		{
			if (this.classTypeCrossRef.hasOwnProperty(classBean.type))	
			{
				if (this.classTypeCrossRef[classBean.type].getItemIndex(classBean.name) >= 0)
				{
					return
				}
				else 
				{
					this.classTypeCrossRef[classBean.type].addItem(classBean.name);
					return
				}
			}
			else
			{
				this.classTypeCrossRef[classBean.type] = new ArrayCollection;
				this.classTypeCrossRef[classBean.type].addItem(classBean.name);
				return;
			}
		}
		
		
		//####################################################
		//	singleton
		//####################################################	
		public function versi(){ }
		
		private static var _instance:versi;
		public static function getInstance():versi
		{
			if( !_instance ){
				_instance = new versi();
			} 
			return _instance;
		}
				
	}
}